/*
 * Copyright (c) 2010, Michael Hengler (developer (at) hengler (dot) de),
 * Nikolaus Moll (developer (at) miblounge (dot) net).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * * Neither the name of the Jombi project nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL JOMBI BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package net.miblounge.jombi.impl.widgets;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jowidgets.common.image.IImageConstant;
import org.jowidgets.common.types.Markup;
import org.jowidgets.common.types.Position;
import org.jowidgets.common.types.SelectionPolicy;
import org.jowidgets.common.widgets.controller.ITreeNodeListener;
import org.jowidgets.spi.impl.controller.TreeSelectionObservableSpi;
import org.jowidgets.spi.widgets.ITreeNodeSpi;
import org.jowidgets.spi.widgets.ITreeSpi;
import org.jowidgets.spi.widgets.controller.ITreeSelectionListenerSpi;
import org.jowidgets.spi.widgets.setup.ITreeSetupSpi;

import com.trolltech.qt.gui.QAbstractItemView.SelectionBehavior;
import com.trolltech.qt.gui.QAbstractItemView.SelectionMode;
import com.trolltech.qt.gui.QContextMenuEvent;
import com.trolltech.qt.gui.QTreeWidget;
import com.trolltech.qt.gui.QTreeWidgetItem;
import com.trolltech.qt.gui.QWidget;

public class TreeImpl extends QtControl implements ITreeSpi, ITreeNodeSpi {

	private final boolean multiSelection;
	private final Map<QTreeWidgetItem, TreeNodeImpl> items;
	private final TreeSelectionObservableSpi treeObservable;

	private List<QTreeWidgetItem> lastSelection;

	public TreeImpl(final Object parentUiReference, final ITreeSetupSpi setup) {
		super(new QTreeWidget((QWidget) parentUiReference));

		getUiReference().setStyleSheet("QTreeWidget { border: 0px }");

		this.lastSelection = new LinkedList<QTreeWidgetItem>();
		this.treeObservable = new TreeSelectionObservableSpi();
		this.items = new HashMap<QTreeWidgetItem, TreeNodeImpl>();
		getUiReference().setSelectionBehavior(SelectionBehavior.SelectItems);

		this.multiSelection = setup.getSelectionPolicy() == SelectionPolicy.MULTI_SELECTION;
		if (multiSelection) {
			getUiReference().setSelectionMode(SelectionMode.ExtendedSelection);
		}

		getUiReference().setHeaderHidden(true);
		// style: setup

		getUiReference().itemSelectionChanged.connect(this, "eventItemSelectionChanged()");
		getUiReference().itemExpanded.connect(this, "eventItemNodeChanged(QTreeWidgetItem)");
		getUiReference().itemCollapsed.connect(this, "eventItemNodeChanged(QTreeWidgetItem)");

	}

	public void contextMenuEvent(final QContextMenuEvent event) {
		final QTreeWidgetItem item = getUiReference().itemAt(event.pos());
		if (item == null) {
			getPopupDetectionObservable().firePopupDetected(new Position(event.pos().x(), event.pos().y()));
		} else {
			final TreeNodeImpl itemImpl = items.get(item);
			itemImpl.firePopupDetected(new Position(event.pos().x(), event.pos().y()));
		}
	}

	protected void eventItemSelectionChanged() {
		fireSelectionChange(getUiReference().selectedItems());
	}

	protected void eventItemNodeChanged(final QTreeWidgetItem current) {
		final TreeNodeImpl itemImpl = items.get(current);
		if (itemImpl != null) {
			itemImpl.fireExpandedChanged(current.isExpanded());
		} else {
			throw new IllegalStateException("No item impl registered for item '" + current + "'. This seems to be a bug");
		}
	}

	@Override
	public QTreeWidget getUiReference() {
		return (QTreeWidget) super.getUiReference();
	}

	@Override
	public ITreeNodeSpi getRootNode() {
		return this;
	}

	@Override
	public List<ITreeNodeSpi> getSelectedNodes() {
		final List<ITreeNodeSpi> result = new LinkedList<ITreeNodeSpi>();
		for (final QTreeWidgetItem item : getUiReference().selectedItems()) {
			result.add(items.get(item));
		}
		return result;
	}

	@Override
	public ITreeNodeSpi addNode(final Integer index) {
		final TreeNodeImpl result = new TreeNodeImpl(this, null, index);
		registerItem(result.getUiReference(), result);
		return result;
	}

	@Override
	public void removeNode(final int index) {
		final QTreeWidgetItem child = getUiReference().takeTopLevelItem(index);
		if (child != null) {
			unRegisterItem(child);
		}
	}

	@Override
	public void addTreeSelectionListener(final ITreeSelectionListenerSpi listener) {
		treeObservable.addTreeSelectionListener(listener);
	}

	@Override
	public void removeTreeSelectionListener(final ITreeSelectionListenerSpi listener) {
		treeObservable.removeTreeSelectionListener(listener);
	}

	@Override
	public void setMarkup(final Markup markup) {
		throw new UnsupportedOperationException("setMarkup is not possible on the root node");
	}

	@Override
	public void setExpanded(final boolean expanded) {
		throw new UnsupportedOperationException("setExpanded is not possible on the root node");
	}

	@Override
	public boolean isExpanded() {
		throw new UnsupportedOperationException("isExpanded is not possible on the root node");
	}

	@Override
	public void setSelected(final boolean selected) {
		throw new UnsupportedOperationException("setSelected is not possible on the root node");
	}

	@Override
	public boolean isSelected() {
		throw new UnsupportedOperationException("isSelected is not possible on the root node");
	}

	@Override
	public void setText(final String text) {
		throw new UnsupportedOperationException("setText is not possible on the root node");
	}

	@Override
	public void setToolTipText(final String text) {
		throw new UnsupportedOperationException("setToolTipText is not possible on the root node");
	}

	@Override
	public void setIcon(final IImageConstant icon) {
		throw new UnsupportedOperationException("setIcon is not possible on the root node");
	}

	@Override
	public void addTreeNodeListener(final ITreeNodeListener listener) {
		throw new UnsupportedOperationException("addTreeNodeListener is not possible on the root node");
	}

	@Override
	public void removeTreeNodeListener(final ITreeNodeListener listener) {
		throw new UnsupportedOperationException("removeTreeNodeListener is not possible on the root node");
	}

	protected void setSelected(final TreeNodeImpl treeNode, final boolean selected) {
		if (selected != treeNode.isSelected()) {
			treeNode.getUiReference().setSelected(selected);
		}
	}

	protected void registerItem(final QTreeWidgetItem item, final TreeNodeImpl treeNodeImpl) {
		items.put(item, treeNodeImpl);
	}

	protected void unRegisterItem(final QTreeWidgetItem item) {
		items.remove(item);
	}

	private void fireSelectionChange(final List<QTreeWidgetItem> newSelection) {
		final List<QTreeWidgetItem> newSelectionList = newSelection;

		for (final QTreeWidgetItem wasSelected : lastSelection) {
			if (!newSelectionList.contains(wasSelected)) {
				items.get(wasSelected).fireSelectionChanged(false);
			}
		}

		for (final QTreeWidgetItem isSelected : newSelectionList) {
			if (!lastSelection.contains(isSelected)) {
				items.get(isSelected).fireSelectionChanged(true);
			}
		}

		lastSelection = newSelectionList;
		treeObservable.fireSelectionChanged();
	}

}
